访问修饰符
访问修饰符 | 同一个类 | 同包 | 不同包,子类 | 不同包,非子类 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
Java中对象及对象引用变量
Java中的对象的引用和对象有些类似C++中的地址和地址的指向的关系。
1 | public class Information { |
我们先定义一个Information类。
然后我们创建一个Infromation对象:
1 | Information stu1 = new Information(); |
这个操作的本质是:
new Information
是以Information类为模板,在堆空间里创建一个Information类对象。- 末尾的()意味着,在对象创建后,立即调用Information类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。
Information stu1
是创建一个Information类引用变量。=
使对象引用指向刚创建的那个Information对象。
我们可以把stu1类比于C++中的地址。
1 | Information stu2 = stu1; |
此时stu2和stu1都指向刚创建的那个Information对象。
1 | Information stu2 = new Information(); |
这样之前的那个Information类就会被回收。
Integer和int
Java的两种数据类型:
- 基本类型:基本数据类类型存的是数值本身。有byte, short, int, long, float, double, boolean, char。
- 引用类型:引用类型变量在内存放的是数据的引用。比如上面定义的Information,以及标题提及的Integer。
下面我们重点讨论”==”这个运算符。
先给出如下表达式的值:
1 | int a = 1000; |
int是基本类型,他的 == 比较是基于数值本身的。
Integer是引用类型,本质是个类,因此它还有equals()
函数可以进行比较,也可以用 == 来进行比较。equals()
比较是基于数值的; 而Integer
的 == 比较是基于引用地址的。
现在又有一个问题,为什么c == d
是true呢?
原因是这样的:
当我们给一个Integer赋予一个int类型的时候会调用Integer的静态方法valueOf()
。也就是说,Integer c = 10;相当于Integer c = Integer.valueOf(10);
jdk1.5的时候引进了自动拆包,装包这个功能。上面这种情况就是自动打包出现的。
具体来看看Integer.valueOf()
的源码:
1 | public static Integer valueOf(int i) { |
从上面我们可以知道给Interger
赋予的int
数值在-128 - 127的时候,直接从cache中获取,这些cache引用对Integer
对象地址是不变的,但是不在这个范围内的数字,则new Integer(i)
这个地址是新的地址,不可能一样的。
所以上个例子中c和d都是IntegerCache的引用,故c == d
为true。而cache中没有1000,故e == f
为false。
而int
和Integer
比较时,Integer
会自动拆包,转化为int和其比较。
final关键字
- 被final修饰的类不可以被继承,final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
- 被final修饰的方法不可以被重写
- 被final修饰基本数据类型和String类型的变量为常量,不可以被修改,且在编译阶段会存入调用类的常量池中
- 被final修饰的引用类型变量不可以改变引用的指向,指向的对象的内容是可以改变的
1 | public class test { |
输出为
1 | 1 2 3 |
static关键字(待重新整理)
首先说this。
this首先是一个对象,它代表调用这个函数的对象。
根据面向对象的基本语法,每当调用变量或者函数的时候,都要按照类名.变量(函数) 的格式来调用。
在不会产生混淆的地方, this是可以省略的。
1 | class Name { |
static 方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用,但是可以定义一个对象,通过对象来访问。
抽象方法和抽象类
一个类没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
当你想构造一个父类方法,他的具体实现由子类来定义,那么这个方法可以定义为抽象方法,当然,普通方法也可以。
抽象类和抽象方法用abstrct
来修饰。
抽象类和方法的性质:
抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
构造方法,类方法(用
static
修饰的方法)不能声明为抽象方法。抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
抽象方法必须为public或者protected,缺省默认public。
1 | abstract class func { |
其中function3()会有一个warning:
The method function3() from the type func is never used locally
再建立一个func2类继承于func:
1 | class func2 extends func { |
会有一个error:
The type func2 must implement the inherited abstract method func.function1()
就是说我们必须重写function1()。
重写:
1 | class func2 extends func { |
定义:
1 | func2 f = new func2(); |
输出:
1 | 1 in func2 |
抽象类和接口
接口(interface) 在java中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而继承接口的抽象方法。
接口的定义方式如下:
1 | interface InterfaceName { |
接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final
变量(final类型详见第4节),而方法会被隐式地指定为public abstract
方法。
要让一个类遵循某组特地的接口需要使用implements关键字,具体格式如下:
1 | class ClassName implements Interface1,Interface2,...{ |
另外,一个接口必须声明在同名的.java中。
同时,接口还可以继承接口。
实例:
test.java
1 | package test; |
test2.java
1 | package test; |
Test3.java
1 | package test; |
总的来说,抽象类和接口的区别有:抽象类是一个类,里边可以包含抽象方法和非抽象方法、抽象变量和非抽象变量,而接口是方法的集合,只包含方法。一个类可以有多个接口,但只能有一个父类。
可变对象和不可变对象
概念
不可变对象(Immutable Objects):对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,任何对它的改变都应该产生一个新的对象。比如String。
可变对象(Mutable Objects):相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。比如StringBuilder。
如何构造不可变类
- 类添加final修饰符,保证类不被继承。
如果类可以被继承会破坏类的不可变性机制,只要继承类覆盖父类的方法并且继承类可以改变成员变量值,那么一旦子类以父类的形式出现时,不能保证当前类是否可变。 - 保证所有成员变量必须私有,并且加上final修饰。
通过这种方式保证成员变量不可改变。但只做到这一步还不够,因为如果是对象成员变量有可能再外部改变其值。所以第4点弥补这个不足。 - 不提供改变成员变量的方法,包括setter。
避免通过其他接口改变成员变量的值,破坏不可变特性。 - 通过构造器初始化所有成员,进行深拷贝(deep copy)。
如果构造器传入的对象直接赋值给成员变量,还是可以通过对传入对象的修改进而导致改变内部变量的值。例如:1
2
3
4
5
6public final class MyImmutableDemo {
private final int[] myArray;
public MyImmutableDemo(int[] array) {
this.myArray = array.clone();
}
}